package model

import (
	"context"
	"github.com/zeromicro/go-zero/core/stores/cache"
	"github.com/zeromicro/go-zero/core/stores/monc"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/bson/primitive"
	"go.mongodb.org/mongo-driver/mongo"
	"time"
)

var _ UserModel = (*customUserModel)(nil)

type (
	// UserModel is an interface to be customized, add more methods here,
	// and implement the added methods in customUserModel.
	UserModel interface {
		userModel
		InsertByOpenId(ctx context.Context, data *User) error
		UpdateByOpenId(ctx context.Context, data *User) (*mongo.UpdateResult, error)
		FindOneByOpenId(ctx context.Context, openid string) (*User, error)
		DeleteByOpenId(ctx context.Context, openid string) (int64, error)

		InsertByMobile(ctx context.Context, data *User) error
		UpdateByMobile(ctx context.Context, data *User) (*mongo.UpdateResult, error)
		FindOneByMobile(ctx context.Context, openid string) (*User, error)
		DeleteByMobile(ctx context.Context, openid string) (int64, error)

		UpdateAvatarByOpenId(ctx context.Context, data *User) (*mongo.UpdateResult, error)
		UpdateAvatarByMobile(ctx context.Context, data *User) (*mongo.UpdateResult, error)
		UpdatePasswdByOpenId(ctx context.Context, data *User) (*mongo.UpdateResult, error)
		UpdatePasswdByMobile(ctx context.Context, data *User) (*mongo.UpdateResult, error)
	}

	customUserModel struct {
		*defaultUserModel
	}
)

// openid
func (c *customUserModel) InsertByOpenId(ctx context.Context, data *User) error {
	if data.ID.IsZero() {
		data.ID = primitive.NewObjectID()
		data.CreateAt = time.Now()
		data.UpdateAt = time.Now()
	}

	key := prefixUserCacheKey + data.OpenID
	_, err := c.conn.InsertOne(ctx, key, data)
	return err
}

func (c *customUserModel) UpdateByOpenId(ctx context.Context, data *User) (*mongo.UpdateResult, error) {
	data.UpdateAt = time.Now()
	key := prefixUserCacheKey + data.OpenID
	return c.conn.UpdateOne(ctx, key, bson.M{"openid": data.OpenID}, bson.M{"$set": data})
}

func (c *customUserModel) FindOneByOpenId(ctx context.Context, openid string) (*User, error) {
	key := prefixUserCacheKey + openid
	var data User
	err := c.conn.FindOne(ctx, key, &data, bson.M{"openid": openid})
	switch err {
	case nil:
		return &data, nil
	case monc.ErrNotFound:
		return nil, ErrNotFound
	default:
		return nil, err
	}
}

func (c *customUserModel) DeleteByOpenId(ctx context.Context, openid string) (int64, error) {
	key := prefixUserCacheKey + openid
	return c.conn.DeleteOne(ctx, key, bson.M{"openid": openid})
}

// mobile
func (c *customUserModel) InsertByMobile(ctx context.Context, data *User) error {
	if data.ID.IsZero() {
		data.ID = primitive.NewObjectID()
		data.CreateAt = time.Now()
		data.UpdateAt = time.Now()
	}

	key := prefixUserCacheKey + data.Mobile
	_, err := c.conn.InsertOne(ctx, key, data)
	return err
}

func (c *customUserModel) UpdateByMobile(ctx context.Context, data *User) (*mongo.UpdateResult, error) {
	data.UpdateAt = time.Now()
	key := prefixUserCacheKey + data.Mobile
	return c.conn.UpdateOne(ctx, key, bson.M{"mobile": data.Mobile}, bson.M{"$set": data})
}

func (c *customUserModel) FindOneByMobile(ctx context.Context, mobile string) (*User, error) {
	key := prefixUserCacheKey + mobile
	var data User
	err := c.conn.FindOne(ctx, key, &data, bson.M{"mobile": mobile})
	switch err {
	case nil:
		return &data, nil
	case monc.ErrNotFound:
		return nil, ErrNotFound
	default:
		return nil, err
	}
}

func (c *customUserModel) DeleteByMobile(ctx context.Context, mobile string) (int64, error) {
	key := prefixUserCacheKey + mobile
	return c.conn.DeleteOne(ctx, key, bson.M{"mobile": mobile})
}

func (c *customUserModel) UpdateAvatarByOpenId(ctx context.Context, data *User) (*mongo.UpdateResult, error) {
	data.UpdateAt = time.Now()
	key := prefixUserCacheKey + data.OpenID
	return c.conn.UpdateOne(ctx, key, bson.M{"openid": data.OpenID}, bson.M{"$set": bson.M{"avatar": data.Avatar}})
}
func (c *customUserModel) UpdateAvatarByMobile(ctx context.Context, data *User) (*mongo.UpdateResult, error) {
	data.UpdateAt = time.Now()
	key := prefixUserCacheKey + data.Mobile
	return c.conn.UpdateOne(ctx, key, bson.M{"mobile": data.Mobile}, bson.M{"$set": bson.M{"avatar": data.Avatar}})
}

func (c *customUserModel) UpdatePasswdByOpenId(ctx context.Context, data *User) (*mongo.UpdateResult, error) {
	data.UpdateAt = time.Now()
	key := prefixUserCacheKey + data.OpenID
	return c.conn.UpdateOne(ctx, key, bson.M{"openid": data.OpenID}, bson.M{"$set": bson.M{"password": data.Password}})
}
func (c *customUserModel) UpdatePasswdByMobile(ctx context.Context, data *User) (*mongo.UpdateResult, error) {
	data.UpdateAt = time.Now()
	key := prefixUserCacheKey + data.Mobile
	return c.conn.UpdateOne(ctx, key, bson.M{"mobile": data.Mobile}, bson.M{"$set": bson.M{"password": data.Password}})
}

// NewUserModel returns a model for the mongo.
func NewUserModel(url, db, collection string, c cache.CacheConf) UserModel {
	conn := monc.MustNewModel(url, db, collection, c)
	return &customUserModel{
		defaultUserModel: newDefaultUserModel(conn),
	}
}
